package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.mapper.ExchangeCodeMapper;
import com.tianji.promotion.service.ExchangeCodeService;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.tianji.promotion.constants.PromotionConstants.*;
import static com.tianji.promotion.utils.CodeUtil.generateCode;

/**
 * @author Cammy
 * @description 针对表【exchange_code(兑换码)】的数据库操作Service实现
 * @createDate 2025-02-25 06:00:15
 */
@Service
public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, ExchangeCode>
        implements ExchangeCodeService {

    private final StringRedisTemplate redisTemplate;
    private final BoundValueOperations<String, String> serialOps;

    public ExchangeCodeServiceImpl(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.serialOps = redisTemplate.boundValueOps(COUPON_CODE_SERIAL_KEY);
    }

    /**
     * TODO:调用兑换码服务的异步生成兑换码方法
     * @param coupon
     */
    @Override
    @Async("generateExchangeCodeExecutor")
    public void asyncGenerateCode(Coupon coupon) {
        // 从优惠券对象中获取发放的兑换码总数量
        Integer totalNum = coupon.getTotalNum();

        // 1.获取 Redis 自增序列号
        // 调用 Redis 的自增操作，自增的步长为 totalNum，返回自增后的结果
        Long increment = serialOps.increment(totalNum);
        // 如果自增操作失败，返回 null，直接结束方法
        if (increment == null) {
            return;
        }
        // 将自增后的结果转换为整数类型，作为最大序列号
        int maxSerialNum = increment.intValue();
        // 创建一个列表，用于存储生成的兑换码对象，初始容量为 totalNum
        List<ExchangeCode> list = new ArrayList<>(totalNum);
        // 循环生成兑换码
        for (int serialNum = maxSerialNum - totalNum + 1; serialNum <= maxSerialNum; serialNum++) {
            // 2.生成兑换码
            // 调用工具类的方法，根据序列号和优惠券 ID 生成兑换码
            String code = generateCode(serialNum, coupon.getId());
            // 创建一个兑换码对象
            ExchangeCode exchangeCode = new ExchangeCode();
            // 设置兑换码的值
            exchangeCode.setCode(code);
            // 设置兑换码的 ID 为序列号
            exchangeCode.setId(maxSerialNum);
            // 设置兑换码关联的优惠券 ID
            exchangeCode.setExchangeTargetId(coupon.getId());
            // 设置兑换码的过期时间为优惠券的发放结束时间
            exchangeCode.setExpiredTime(coupon.getIssueEndTime());
            // 将生成的兑换码对象添加到列表中
            list.add(exchangeCode);
        }

        // 3.保存数据库
        // 调用批量保存方法，将生成的兑换码列表保存到数据库中
        saveBatch(list);

        // 4.写入 Redis 缓存，member：couponId，score：兑换码的最大序列号
        // 使用 Redis 的有序集合操作，将优惠券 ID 作为成员，最大序列号作为分数添加到有序集合中
        redisTemplate.opsForZSet().add(COUPON_RANGE_KEY, coupon.getId().toString(), maxSerialNum);
    }


    /**
     * TODO:更新兑换码标记状态
     *
     * @param serialNum 兑换码的序列号，用于定位要更新标记的具体位置
     * @param mark 要设置的标记状态，true 或 false
     * @return 如果更新操作成功，则返回 true；否则返回 false
     */
    @Override
    public boolean updateExchangeMark(long serialNum, boolean mark) {
        // 调用 Redis 操作，使用 setBit 方法设置指定键（COUPON_CODE_MAP_KEY）
        // 在指定序列号（serialNum）位置的二进制位的值为 mark
        // setBit 方法返回一个布尔值，表示操作是否成功
        Boolean boo = redisTemplate.opsForValue().setBit(COUPON_CODE_MAP_KEY, serialNum, mark);
        // 检查返回值是否为 null 以及是否为 true
        // 如果返回值不为 null 且为 true，则表示更新操作成功，返回 true
        // 否则返回 false
        return boo != null && boo;
    }

    /**
     * 查询兑换码对应的优惠券id
     * @param serialNum 兑换码的序列号，用于定位对应的优惠券
     * @return 兑换码对应的优惠券id，如果未找到则返回 null
     */
    @Override
    public Long exchangeTargetId(long serialNum) {
        // 1.查询score值比当前序列号大的第一个优惠券
        // 使用 Redis 的有序集合操作，根据指定的分数范围（serialNum 到 serialNum + 5000）
        // 从有序集合 COUPON_RANGE_KEY 中查询第一个元素，偏移量为 0，获取数量为 1
        Set<String> results = redisTemplate.opsForZSet().rangeByScore(
                COUPON_RANGE_KEY, serialNum, serialNum + 5000, 0L, 1L);
        // 检查查询结果是否为空
        if (CollUtils.isEmpty(results)) {
            // 如果结果为空，说明未找到对应的优惠券，返回 null
            return null;
        }
        // 2.数据转换
        // 获取结果集中的第一个元素
        String next = results.iterator().next();
        // 将获取到的元素转换为 Long 类型并返回
        return Long.parseLong(next);
    }


}




